package com.cloudfun.campusshare.code.generate.definition;

import lombok.Data;
import lombok.SneakyThrows;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/**
 * Created by Huqin on 2020/5/26
 */
@Data
public class MethodDefinition {

    private final JavaFileDefinition javaFileDefinition;

    private MethodCommentDefinition methodCommentDefinition;

    private Class retClazz;
    private String methodName;
    private List<Class> parameters = new ArrayList<>();
    private String body;

    public MethodDefinition(JavaFileDefinition javaFileDefinition) {
        this.javaFileDefinition = javaFileDefinition;
    }

    private void init() {
        if (null == javaFileDefinition) {
            throw new RuntimeException("javaFileDefinition can not be null");
        }
        if (null == retClazz) {
            throw new RuntimeException("return class can not be null");
        }
        if (StringUtils.isBlank(methodName)) {
            throw new RuntimeException("method name can not be null");
        }
    }

    @SneakyThrows
    public String toString() {
        this.init();
        StringBuilder sb = new StringBuilder();
        // 注释
        if (null != methodCommentDefinition) {
            sb.append(methodCommentDefinition.toString());
            sb.append("\n");
        }
        // Override
        if (null != javaFileDefinition.getSuperClass()) {
            Method[] declaredMethods = javaFileDefinition.getSuperClass().getDeclaredMethods();
            if (!javaFileDefinition.isInterface()) {
                boolean overrideMethod = false;
                for (Method declaredMethod : declaredMethods) {
                    if (declaredMethod.getName().equals(methodName) &&
                            Arrays.stream(declaredMethod.getParameterTypes()).map(Class::getName).collect(Collectors.toSet())
                                    .equals(parameters.stream().map(Class::getName).collect(Collectors.toSet()))) {
                        overrideMethod = true;
                    }
                }
                if (overrideMethod) {
                    sb.append("@Override\n");
                }
            }
        }
        if (!CollectionUtils.isEmpty(javaFileDefinition.getImplClasses())) {
            for (String implClass : javaFileDefinition.getImplClasses()) {
                Method[] declaredMethods = Class.forName(implClass).getDeclaredMethods();
                if (!javaFileDefinition.isInterface()) {
                    boolean overrideMethod = false;
                    for (Method declaredMethod : declaredMethods) {
                        if (declaredMethod.getName().equals(methodName) &&
                                Arrays.stream(declaredMethod.getParameterTypes()).map(Class::getName).collect(Collectors.toSet())
                                        .equals(parameters.stream().map(Class::getName).collect(Collectors.toSet()))) {
                            overrideMethod = true;
                        }
                    }
                    if (overrideMethod) {
                        sb.append("    @Override\n");
                    }
                }
            }
        }

        sb.append(JavaFileDefinition.INDENTS);
        if (!javaFileDefinition.isInterface()) {
            sb.append("public ");
        }
        if (this.getRetClazz().equals(Void.class)) {
            sb.append(this.getRetClazz().getSimpleName().toLowerCase()).append(" ");
        } else {
            sb.append(this.getRetClazz().getSimpleName()).append(" ");
        }
        sb.append(this.getMethodName());
        String parameters = this.getParameters().stream().map(parameter -> {
            String paraClazz = parameter.getSimpleName();
            String paraName = StringUtils.uncapitalize(paraClazz);
            return paraClazz + " " + paraName;
        }).collect(Collectors.joining(", "));
        sb.append("(").append(parameters).append(")");
        if (javaFileDefinition.isInterface()) {
            sb.append(";\n");
        } else {
            sb.append("{\n");
            sb.append(body);
            sb.append("\n");
            sb.append(JavaFileDefinition.INDENTS);
            sb.append("}\n");
        }
        return sb.toString();
    }
}
